#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <portaudio.h>

#include "au.h"
#include "adev.h"
#include "common.h"

enum {
	A_NULL,
	A_ENC,
	A_RATE,
	A_CHAN
};

struct adev {
	PaStreamParameters	 param;
	PaStream		*stream;
	const char		*device;
	uint32_t		 enc;
	uint32_t		 rate;
	int			 chanmax;
	int			 mode;
};

const struct a_lookup stab[] = {
	{ "enc", A_ENC },
	{ "encoding", A_ENC },
	{ "rate", A_RATE },
	{ "chan", A_CHAN },
	{ "channel", A_CHAN },
	{ "channels", A_CHAN },
	{ NULL, A_NULL }
};

struct adev *
ad_open(const char *dev, int mode)
{
	struct adev *a;
	const PaDeviceInfo *di;
	PaDeviceIndex i;
	PaDeviceIndex n;

	if (Pa_Initialize() != paNoError)
		return (NULL);
	if ((a = malloc(sizeof(struct adev))) == NULL) {
		Pa_Terminate();
		return (NULL);
	}
	a->stream = NULL;
	a->device = dev;
	a->enc = AU_PCM16;
	a->rate = 48000;
	a->mode = mode == A_READ;
	if (a->device != NULL) {
		if ((n = Pa_GetDeviceCount()) < 0)
			goto err;
		for (i = 0; i < n; i++) {
			if ((di = Pa_GetDeviceInfo(i)) == NULL)
				goto err;
			if (strcmp(di->name, a->device) == 0)
				break;
		}
		if (i == n)
			goto err;
		a->param.device = i;
	} else {
		if ((a->param.device = a->mode ?
		    Pa_GetDefaultInputDevice() :
		    Pa_GetDefaultOutputDevice()) < 0)
			goto err;
		if ((di = Pa_GetDeviceInfo(a->param.device)) == NULL)
			goto err;
	}
	a->chanmax = a->mode ? di->maxInputChannels :
	    di->maxOutputChannels;
	a->param.channelCount = MIN(a->chanmax, 2);
	a->param.sampleFormat = paInt16;
	a->rate = di->defaultSampleRate;
	a->param.suggestedLatency = a->mode ?
	    di->defaultHighInputLatency : di->defaultHighOutputLatency;
	a->param.hostApiSpecificStreamInfo = NULL;
	return (a);
err:
	ad_close(a);
	return (NULL);
}

struct adev *
ad_fpopen(FILE *fp, int mode)
{

	ERR(EOPNOTSUPP, NULL);
}

void
ad_close(struct adev *a)
{

	if (a == NULL)
		return;
	if (a->stream != NULL) {
		if (Pa_IsStreamActive(a->stream) == 1)
			(void)Pa_StopStream(a->stream);
		(void)Pa_CloseStream(a->stream);
	}
	Pa_Terminate();
	free(a);
}

int
ad_limit(struct adev *a)
{

	ERR(ENOSYS, -1);
}

int
ad_get(struct adev *a, const char *s, ...)
{
	va_list ap;
	ssize_t r;
	int n;
	int v;

	if (a == NULL || s == NULL)
		ERR(EINVAL, -1);
	va_start(ap, s);
	for (n = 0; (r = a_lookup(stab, s, &v)) > 0; n++, s += r)
		switch (v) {
		case A_ENC:
			*va_arg(ap, uint32_t *) = a->enc;
			break;
		case A_RATE:
			*va_arg(ap, uint32_t *) = a->rate;
			break;
		case A_CHAN:
			*va_arg(ap, uint32_t *) = a->param.channelCount;
			break;
		default:
			goto err;
		}
	if (r == 0) {
		va_end(ap);
		return (n);
	}
err:
	va_end(ap);
	ERR(EINVAL, n);
}

int
ad_set(struct adev *a, const char *s, ...)
{
	va_list ap;
	ssize_t r;
	int n;
	int v;

	if (a == NULL || s == NULL)
		ERR(EINVAL, -1);
	if (a->stream != NULL) {
		if (Pa_IsStreamActive(a->stream) == 1)
			(void)Pa_StopStream(a->stream);
		(void)Pa_CloseStream(a->stream);
		a->stream = NULL;
	}
	va_start(ap, s);
	for (n = 0; (r = a_lookup(stab, s, &v)) > 0; n++, s += r)
		switch (v) {
		case A_ENC:
			switch ((a->enc = va_arg(ap, uint32_t))) {
			case AU_ULAW:
				a->param.sampleFormat = paInt16;
				a->enc = AU_PCM16;
				goto err;
			case AU_PCM8:
				a->param.sampleFormat = paInt8;
				a->enc = AU_PCM8;
				break;
			case AU_PCM16:
				a->param.sampleFormat = paInt16;
				a->enc = AU_PCM16;
				break;
			case AU_PCM24:
				a->param.sampleFormat = paInt24;
				a->enc = AU_PCM24;
				goto err;
			case AU_PCM32:
				a->param.sampleFormat = paInt32;
				a->enc = AU_PCM32;
				break;
			case AU_FLOAT32:
				a->param.sampleFormat = paFloat32;
				a->enc = AU_FLOAT32;
				break;
			case AU_FLOAT64:
				a->param.sampleFormat = paFloat32;
				a->enc = AU_FLOAT32;
				goto err;
			case AU_ALAW:
				a->param.sampleFormat = paInt16;
				a->enc = AU_PCM16;
				goto err;
			default:
				goto err;
			}
			break;
		case A_RATE:
			a->rate = va_arg(ap, uint32_t);
			break;
		case A_CHAN:
			a->param.channelCount = va_arg(ap, uint32_t);
			break;
		default:
			goto err;
		}
	if (r == 0) {
		va_end(ap);
		return (n);
	}
err:
	va_end(ap);
	ERR(EINVAL, n);
}

ssize_t
ad_read(struct adev *a, void *b, size_t s)
{
	size_t fc;

	if (a == NULL || b == NULL || !a->mode)
		ERR(EINVAL, -1);
	s -= s % (au_encsize(a->enc) * a->param.channelCount);
	if (s == 0)
		return (0);
	if (a->stream == NULL) {
		if (Pa_OpenStream(&a->stream, &a->param, NULL, a->rate,
		    paFramesPerBufferUnspecified, 0, NULL, NULL)
		    != paNoError)
			goto err;
		if (Pa_StartStream(a->stream) != paNoError) {
			(void)Pa_CloseStream(a->stream);
			a->stream = NULL;
			goto err;
		}
	}
	fc = s / au_encsize(a->enc) / a->param.channelCount;
	(void)Pa_ReadStream(a->stream, b, fc);
	au_encnswap(a->enc, b, s);
	return (s);
err:
	return (-1);
}

ssize_t
ad_write(struct adev *a, void *b, size_t s)
{
	size_t fc;

	if (a == NULL || b == NULL || a->mode)
		ERR(EINVAL, -1);
	s -= s % (au_encsize(a->enc) * a->param.channelCount);
	if (s == 0)
		return (0);
	if (a->stream == NULL) {
		if (Pa_OpenStream(&a->stream, NULL, &a->param, a->rate,
		    paFramesPerBufferUnspecified, 0, NULL, NULL)
		    != paNoError)
			goto err;
		if (Pa_StartStream(a->stream) != paNoError) {
			(void)Pa_CloseStream(a->stream);
			a->stream = NULL;
			goto err;
		}
	}
	fc = s / au_encsize(a->enc) / a->param.channelCount;
	au_encnswap(a->enc, b, s);
	(void)Pa_WriteStream(a->stream, b, fc);
	au_encnswap(a->enc, b, s);
	return (s);
err:
	return (-1);
}
